////////////////////////////////////////////////////////////////////////////////
/// Project: GTest extension (GTestEX)
///
/// \file gtestex.h
///
/// \brief The purpose of this class is to add cross-os support for
/// for stream data
///
/// \version 0.1.0
///
/// \date   May 27, 2011
///
/// \author contact@afonseca.org (Armando Fonseca)
///
/// \attention TCPClient and GTestListener are based on the StreamListener
//  classes from the Google Test framework.
////////////////////////////////////////////////////////////////////////////////

#include "gtestex.h"

/// \namespace gtestex Encapsulate components for extending GTest.
namespace gtestex {
/// \namespace msg_transmit functionalities to establish communication between
///  gtest and gtestex.
namespace msg_transmit {

////////////////////////////////////////////////////////////////////////////////
// WINDOWS AND MINGW
////////////////////////////////////////////////////////////////////////////////
#if GTEST_OS_WINDOWS_MINGW || GTEST_OS_WINDOWS

// Close connection (Operating System dependent)
int TCPClient::OSCloseConnection() {
  // Checking If there is a tcp client already running
  if (!IsConnected()) {
    GTEST_LOG_(WARNING) << "OSCloseConnection() can't be called when "
                        << "there is already a server.";
    return 0;
  }

  int reason_code0 = closesocket(sockfd());
  if (0 != reason_code0) {
    GTEST_LOG_(WARNING) << "OSCloseConnection() closesocket failed.";
    return reason_code0;
  }

  int reason_code1 = WSACleanup();
  if (0 != reason_code1) {
    GTEST_LOG_(WARNING) << "OSCloseConnection() WSACleanup failed, error_code: "
                         << WSAGetLastError();
    return reason_code1;
  }

  set_sockfd(kInvalidSockFd);

  return 0;
}

// Open a TCP connection (Operating System dependent)
int TCPClient::OSOpenConnetion() {
  //----------------------------------------------------------------------------
  // Checking If there is a tcp client already running
  if (IsConnected()) {
    GTEST_LOG_(WARNING) << "OSOpenConnetion() can't be called when "
                        << "there is already a client.";
    return 0;
  }

  //----------------------------------------------------------------------------
  // Start Winsock
  WSAData winsock_data;
  int reason_code0 = WSAStartup(MAKEWORD(1, 1), &winsock_data);
  if (0 != reason_code0) {
    GTEST_LOG_(WARNING) << "OSOpenConnetion() WSAStartup failed.";
    return reason_code0;
  }

  //----------------------------------------------------------------------------
  // Get a linked list of IP addresses for the given host name
  addrinfo hints;
  std::memset(&hints, '\0', sizeof(hints));
  hints.ai_family = AF_UNSPEC;  // To allow both IPv4 and IPv6 addresses
  hints.ai_socktype = SOCK_STREAM;
  // hints.ai_flags = AI_PASSIVE;
  addrinfo* servinfo = NULL;
  const int reason_code1 = getaddrinfo(host_name().c_str(), port_num().c_str(),
                                       &hints, &servinfo);
  if ((0 != reason_code1) || (NULL == servinfo)) {
    GTEST_LOG_(WARNING) << "OSOpenConnetion() getaddrinfo failed, "
                        << "return code: " << gai_strerror(reason_code1);
    return reason_code1;
  }

  //----------------------------------------------------------------------------
  // Traverse the linked list of IP addresses obtained from getaddrinfo(),
  // get a socket descriptor associated to the IP address and bind a port number
  for (addrinfo* cur_addr = servinfo;
      kInvalidSockFd == sockfd() && cur_addr != NULL;
       cur_addr = cur_addr->ai_next) {
    // Get a socket descriptor
    set_sockfd(socket(cur_addr->ai_family, cur_addr->ai_socktype,
                      cur_addr->ai_protocol));
    if (kInvalidSockFd != sockfd()) {
      // Connect the client socket to the server socket.
      if (connect(sockfd(), cur_addr->ai_addr, cur_addr->ai_addrlen) == -1) {
        closesocket(sockfd());
        set_sockfd(kInvalidSockFd);
      }
    }
  }

  //----------------------------------------------------------------------------
  // Free the servinfo structure
  freeaddrinfo(servinfo);

  return 0;
}

// Send records to a TCP server (Operating System dependent)
int TCPClient::OSSendMessage(const string& record, bool add_new_line) {
  GTEST_CHECK_(IsConnected())
      << "Send() can be called only when there is a connection.";
  if (IsConnected() && !record.empty()) {
    const string& line = (add_new_line && (*--record.end() == '\n'))
                         ? record : record + '\n';
    const int msg_len = static_cast<int>(line.length());
    const int bytes_sent = send(sockfd(), line.c_str(), msg_len, 0);
    if (0 == bytes_sent) {
      GTEST_LOG_(WARNING) << "OSSendMessage : failed to send record to "
                          << host_name() << ":" << port_num();
    }
    return bytes_sent;
  }
  return -1;
}

#endif  // GTEST_OS_WINDOWS_MINGW || GTEST_OS_WINDOWS

////////////////////////////////////////////////////////////////////////////////
// WINDOWS AND MINGW ENDS
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// OS INDEPENDENT
////////////////////////////////////////////////////////////////////////////////
/// \namespace record_utility utilities to create and format gtestex records
namespace record_utility {

void MakeUnitTestRecord(const RecordId mf_id, const UnitTest& unit_test,
                        const int iteration, std::ostream& record_stream) {
  record_stream << mf_id
                << Delimeter() << iteration
                << Delimeter() << unit_test.successful_test_case_count()
                << Delimeter() << unit_test.successful_test_case_count()
                << Delimeter() << unit_test.successful_test_case_count()
                << Delimeter() << unit_test.failed_test_case_count()
                << Delimeter() << unit_test.total_test_case_count()
                << Delimeter() << unit_test.test_case_to_run_count()
                << Delimeter() << unit_test.successful_test_count()
                << Delimeter() << unit_test.failed_test_count()
                << Delimeter() << unit_test.disabled_test_count()
                << Delimeter() << unit_test.total_test_count()
                << Delimeter() << unit_test.test_to_run_count()
                << Delimeter() << unit_test.Passed()
                << Delimeter() << unit_test.Failed()
                << Delimeter() << unit_test.elapsed_time();
}
  
void MakeTestCaseRecord(const RecordId mf_id, const TestCase& test_case,
                        std::ostream& record_stream) {
  record_stream << mf_id
                << Delimeter() << UrlEncode(test_case.name())
                << Delimeter() << UrlEncode(test_case.type_param())
                << Delimeter() << test_case.should_run()
                << Delimeter() << test_case.successful_test_count()
                << Delimeter() << test_case.failed_test_count()        
                << Delimeter() << test_case.disabled_test_count()
                << Delimeter() << test_case.total_test_count()
                << Delimeter() << test_case.test_to_run_count()
                << Delimeter() << test_case.Passed()
                << Delimeter() << test_case.Failed()
                << Delimeter() << test_case.elapsed_time();
}
  
void MakeTestInfoRecord(const RecordId mf_id, const TestInfo& test_info,
                        std::ostream& record_stream) {
  record_stream << mf_id
                << Delimeter() << UrlEncode(test_info.name()) 
                << Delimeter() << UrlEncode(test_info.type_param())
                << Delimeter() << UrlEncode(test_info.value_param())
                << Delimeter() << test_info.should_run();
                // << Delimeter() << test_info.result(); // TODO do we need it?
}

void MakeTestPartResultRecord(const record_utility::RecordId mf_id, 
    const TestPartResult& test_part_result, std::ostream& record_stream) {
  record_stream << mf_id 
                << Delimeter() << test_part_result.type()
                << Delimeter() << UrlEncode(test_part_result.file_name())
                << Delimeter() << test_part_result.line_number()
                << Delimeter() << UrlEncode(test_part_result.summary())
                << Delimeter() << UrlEncode(test_part_result.message())
                << Delimeter() << test_part_result.passed()
                << Delimeter() << test_part_result.failed()
                << Delimeter() << test_part_result.nonfatally_failed()
                << Delimeter() << test_part_result.fatally_failed();
}

bool IsRecordValid(const string& record) {
  // Check for valid record string:
  // string must not be empty, the first two char must be digits, 
  // and the first field should have a RecordId value or return false otherwise.
  RecordId r_id = kOnInvalidMemberFunction;
  if (record.empty() 
      || (!('0' <= record[0] && '9' >= record[0]) 
            && !('0' <= record[1] && '9' >= record[1]))
      || ((r_id = GetRecordId(record)) == kOnInvalidMemberFunction)) { 
    return false; 
  }
  
  // A records string should have delimeter characters separating fields from
  // each other.
  string::value_type delimeter_value = Delimeter();
  string::const_iterator current = record.begin();
  const string::const_iterator end = record.end();
  std::size_t delimeter_count = 0;
  while(current != end) { 
    if (*current++ == delimeter_value) { ++delimeter_count; }
  }

  // Check if record string is a valid record
  bool is_a_valid_record = false;
  switch(delimeter_count) {
    case 15: {  // UnitTest data contains 15 delimeters
      switch(r_id) {
         case kOnTestProgramStart:
         case kOnTestIterationStart:
         case kOnEnvironmentsSetUpStart:
         case kOnEnvironmentsSetUpEnd:
         case kOnEnvironmentsTearDownStart:
         case kOnEnvironmentsTearDownEnd:
         case kOnTestIterationEnd:
         case kOnTestProgramEnd: 
           is_a_valid_record = true;
           break;
         default:
           assert(false);
           break;
      }
      break;
    }
    case 11: {  // TestCase data contains 11 delimeters
      switch(r_id) {
        case kOnTestCaseStart: 
        case kOnTestCaseEnd:
           is_a_valid_record = true;
           break;
         default: 
           assert(false);
           break;
      }
      break;
    }
    case 4: {  // TestInfo data contains 4 delimeters
      switch(r_id) {
         case kOnTestStart:
         case kOnTestEnd:
           is_a_valid_record = true;
           break;
         default: 
           assert(false);
           break;
      }
      break;
    }
    case 9: {  // TestPartResult data contains 4 delimeters
      switch(r_id) {
        case kOnTestPartResult: 
           is_a_valid_record = true;
           break;
         default: 
           assert(false);
           break;
      }
      break;
    }
    default: { 
      assert(false);
      break;
    }
  }
  return is_a_valid_record;    
}

}  // End of namespace: (record_utility)

void TCPSendRecordListener::send(const string& record) {
 if (record_utility::IsRecordValid(record)) {
   tcp_client_ptr()->SendMessage(record);
 } else { /* TODO log */ }
}

}  // End of namespace: (msg_transmit)
}  // End of namespace: (gtestex)
